<template>
  <div class="MindMapContent">
    <el-dropdown split-button size="small" style="margin-right: 10px" @command="handleCommand">
      {{ mindTypeText }}
      <template #dropdown>
        <el-dropdown-menu>
          <el-dropdown-item command="mindMap">思维导图</el-dropdown-item>
          <el-dropdown-item command="ishikawa">鱼骨图</el-dropdown-item>
          <el-dropdown-item command="organize">组织结构图</el-dropdown-item>
          <el-dropdown-item command="tree">树形图</el-dropdown-item>
          <el-dropdown-item command="vsMap">对比分析图</el-dropdown-item>
          <el-dropdown-item command="spaceMap">空间分析图</el-dropdown-item>
        </el-dropdown-menu>
      </template>
    </el-dropdown>
    <el-button size="small" @click="addNode">新增节点</el-button>
    <el-button size="small" @click="updateNode">编辑节点</el-button>
    <el-button size="small" type="danger" plain @click="removeNode">删除节点</el-button>
    <div id="mindContent" style="height: 600px">
      <div id="container"></div>
    </div>

    <el-dialog v-model="visible" :title="pageType == 'edit' ? '编辑' : '新增'">
      <div>
        <el-form class="search-form" ref="formData" size="small" label-width="120px" :model="formData">
          <el-form-item label="节点名称" prop="label" :rules="[{required: true, message: '请输入节点名称',trigger: 'blur'}]">
            <el-input v-model="formData.label" style="width: 60%"></el-input>
          </el-form-item>
        </el-form>
      </div>
      <span slot="footer" class="dialog-footer">
          <el-button @click="cancelDialog">返回</el-button>
          <el-button type="primary" @click="submitData">提交</el-button>
      </span>
    </el-dialog>
  </div>
</template>

<script>
import { Graph } from '@antv/x6'
import { Selection } from '@antv/x6-plugin-selection'
import { DagreLayout } from '@antv/layout'

export default {
  data(){
    return{
      graph: null, // 画布实例对象
      objData:{},  //
      visible: false,
      pageType: '',   //弹窗的类型，编辑、新增
      formData: {},  //当前节点
      dagreLayout: null,   //思维导图布局
      organizeLayout: null,   //组织结构布局
      mindType: 'mindMap',   //图标类型
      nowData: {},         //点击选中的数据
      //伪造的后端数据
      mindData: {
        // nodeList: [
        //   {id: '50', label: '根节点', fjdId: null, sort: 1},
        //   {id: '71', label: '节点3', fjdId: '50', sort: 2},
        //   {id: '61', label: '节点2', fjdId: '50', sort: 3},
        //   {id: '54', label: '节点1', fjdId: '50', sort: 4},
        //   {id: '66', label: '333', fjdId: '54', sort: 5},
        //   {id: '67', label: '444', fjdId: '61', sort: 6},
        //   {id: '69', label: '555', fjdId: '61', sort: 7}
        // ],
        // edgeList: [
        //   {source: '50',target: '61'},
        //   {source: '50',target: '54',},
        //   {source: '50',target: '71'},
        //   {source: '54',target: '66',},
        //   {source: '61',target: '67'},
        //   {source: '61',target: '69',},
        // ],
        nodeList: [
          {id: '1', label: '对比分析', fjdId: null, sort: 1},
          {id: '2', label: '车辆1', fjdId: '1', sort: 2},
          {id: '21', label: '速度', fjdId: '2', sort: 2},
          {id: '22', label: '重量', fjdId: '2', sort: 2},
          {id: '3', label: '车辆2', fjdId: '1', sort: 3},
          {id: '31', label: '速度', fjdId: '3', sort: 2},
          {id: '32', label: '重量', fjdId: '3', sort: 2},
          {id: '4', label: '车辆3', fjdId: '1', sort: 4},
          {id: '41', label: '速度', fjdId: '4', sort: 2},
          {id: '42', label: '重量', fjdId: '4', sort: 2},
        ],
        edgeList: [
          {source: '1',target: '2'},
          {source: '1',target: '3',},
          {source: '1',target: '4'},
          {source: '2',target: '21',},
          {source: '2',target: '22'},
          {source: '3',target: '31',},
          {source: '3',target: '32'},
          {source: '4',target: '41',},
          {source: '4',target: '42'},
        ],
      }
    }
  },
  computed: {
    mindTypeText(){
      let map = {
        mindMap: '思维导图',
        ishikawa: '鱼骨图',
        organize: '组织结构图',
        tree: '树形图',
        vsMap: '对比分析图',
        spaceMap: '空间分析图'
      }
      return map[this.mindType]
    }
  },
  mounted() {
    this.getNodeData(true)
  },
  methods: {
    // 初始化流程图画布
    initGraph() {
      let container = document.getElementById('container')
      this.graph = null
      this.graph = new Graph({
        container,
        width: '100%',
        height: '100%',
        //最大最小缩放比例
        scaling: {
          min: 0.7,
          max: 1.2
        },
        autoResize: true,
        panning: true,
        mousewheel: true,
        background: {
          color: '#aaaaaa', // 设置画布背景颜色
        },
      })

      this.dagreLayout = new DagreLayout({
        type: 'dagre',
        rankdir: 'LR',
        align: undefined,
        ranksep: 45,
        nodesep: 5,
        controlPoints: true
      })
      this.organizeLayout = new DagreLayout({
        type: 'dagre',
        rankdir: 'TB',
        align: undefined,
        ranksep: 15,
        nodesep: 45,
      })

      //数据上图
      this.handleCommand('mindMap')

      this.graph.use(
          new Selection({
            enabled: true,
            multiple: false,
            movable: false,
            rubberband: false,
            showNodeSelectionBox: true,
            clearSelectionOnBlank: false
          })
      )

      this.graph.on('node:click', ({ e,node }) => {
        e.stopPropagation()
        tooltip.style.display = 'none'
        this.graph.resetSelection(node)
        this.nowData = {
          id: node.id,
          label: node.label,
          portalId: node.data.portalId || ''
        }
        this.$emit('getData',this.nowData)
      })
      this.graph.on('blank:click', ({ e,node }) => {
        this.graph.cleanSelection()
        this.nowData = {}
      })

      const tooltip = document.createElement('div')
      tooltip.className = 'x6-tooltip'
      tooltip.style.position = "absolute"
      tooltip.style.display = 'none'
      tooltip.style.padding = '6px'
      tooltip.style.borderRadius = '5px'
      tooltip.style.backgroundColor = '#303133'
      tooltip.style.color = '#ffffff'
      tooltip.style.fontSize = '12px'
      let mindContent = document.getElementById('mindContent')
      mindContent.appendChild(tooltip)
      this.graph.on('node:mouseenter', ({ node }) => {
        if(node.label){
          const position = this.graph.localToGraph(node.getBBox().getCenter())
          tooltip.style.display = 'block'
          tooltip.style.left = `${position.x - 60}px`
          tooltip.style.top = `${position.y - 50}px`
          tooltip.textContent = node.label
        }
      })
      this.graph.on('node:mouseleave', ({ node }) => {
        tooltip.style.display = 'none'
      })
    },
    //获取子节点
    getNodeChild(item){
      const childNode = this.objData.nodes.filter(node => node.fjdId == item.id)
      if(childNode.length == 0) return []
      let list = [...childNode]
      childNode.forEach(e => {
        list = list.concat(this.getNodeChild(e))
      })
      return list
    },

    //修改图类型
    handleCommand(e){
      this.mindType = e
      if(this.graph){
        const timerDivs = document.querySelectorAll('div.x6-timer');
        timerDivs.forEach(div => {
          div.remove();
        });
        this.graph.clearCells()
      }
      setTimeout(() => {
        let attrs = {
          line: {
            stroke: '#1d6ee4'
          }
        }
        if(this.mindType == 'mindMap'){
          this.objData.edges = (this.objData.edges || []).map(item => {
            return{
              source: item.source, // String，必须，起始节点 id
              target: item.target, // String，必须，目标节点 id
              router: {
                name: 'manhattan',
                args: {
                  startDirections: ['right'],
                  endDirections: ['left']
                }
              },
              attrs
            }
          })
          this.graph.fromJSON(this.dagreLayout.layout(this.objData))
          this.graph.centerContent();
        }
        if(this.mindType == 'organize'){
          this.objData.edges = (this.objData.edges || []).map(item => {
            return{
              source: item.source, // String，必须，起始节点 id
              target: item.target, // String，必须，目标节点 id
              router: {
                name: 'manhattan',
                args: {
                  startDirections: ['bottom'],
                  endDirections: ['top']
                }
              },
              attrs
            }
          })
          this.graph.fromJSON(this.organizeLayout.layout(this.objData))
          this.graph.centerContent();
        }
        if(this.mindType == 'ishikawa'){
          let data = JSON.parse(JSON.stringify(this.objData))
          data.edges = []
          //根节点的坐标为[100,100],根节点下第一个子节点为根节点x坐标偏移150
          let fristX = 250
          let fristY = 100
          let fristId = ''
          //根节点信息
          data.nodes[0].x = 100
          data.nodes[0].y = 100
          fristId = data.nodes[0].id

          let isTop = true
          data.nodes.forEach((item, index) => {
            //计算根节点下子节点坐标
            if(item.fjdId == fristId){
              item.x = fristX
              data.edges.push({
                source: fristId,
                target: item.id,
                vertices: [
                  {x: item.x + 60, y: fristY + 15}
                ],
                attrs
              })
              if(isTop){
                item.y = fristY - 60
              }else{
                item.y = fristY + 60
              }
              //递归展示当前节点下的所有节点
              this.buildFish(data,item,isTop)
              //根据上一个子节点的深度，计算子节点所有层级的宽度，下一个子节点在此基础上计算x坐标
              let nodeWidth = ((this.getNodeDepth(item,data) || 1) * 150)
              fristX += nodeWidth
              isTop = !isTop
            }
          })
          data.edges.push({
            source: fristId,
            target: {x: fristX + 150, y: fristY + 15 },
            attrs
          })
          this.graph.fromJSON(data)
          this.graph.centerContent();
        }
        if(this.mindType == 'tree'){
          let data = JSON.parse(JSON.stringify(this.objData))
          data.edges = []
          //根节点信息
          let fristX = 100
          let fristY = 100 + 40
          let fristId = ''
          data.nodes[0].x = 100
          data.nodes[0].y = 100
          fristId = data.nodes[0].id

          data.nodes.forEach((item, index) => {
            if(item.fjdId == fristId){
              item.x = fristX + 80
              item.y = fristY
              data.edges.push({
                source: fristId,
                target: item.id,
                router: {
                  name: 'manhattan',
                  args: {
                    startDirections: ['bottom'],
                    endDirections: ['left']
                  }
                },
                attrs
              })
              this.buildTree(data,item)
              let nodeHeight = ((this.getNodeNum(item,data) + 1) * 40)
              fristY += nodeHeight
            }
          })
          this.graph.fromJSON(data)
          this.graph.centerContent();
        }

        if(this.mindType == 'vsMap'){
          let data = JSON.parse(JSON.stringify(this.objData))
          let nodes = data.nodes
          let edges = data.edges
          let fristId = data.nodes[0].id

          let secendX = 0
          nodes.forEach(e => {
            if(!e.fjdId) return
            if(e.fjdId == fristId){
              e.x = secendX
              e.y = 100
              secendX += 200
              let childNodes = nodes.filter(_e => _e.fjdId == e.id) || []
              console.log(childNodes)
              let threeY = 0
              childNodes.forEach(childNode => {
                threeY += 40
                childNode.x = e.x + 10
                childNode.y = e.y + threeY
                childNode.width = 110
                childNode.height = 27
              })
            }
          })
          nodes[0].x = ((secendX - 200) / 2)
          nodes[0].y = 20

          edges.forEach((item, index) => {
            if(item.source == fristId){
              item.router = {
                name: 'manhattan',
                args: {
                  startDirections: ['bottom'],
                  endDirections: ['top']
                }
              }
            }else{
              item.router = {
                name: 'manhattan',
                args: {
                  startDirections: ['left'],
                  endDirections: ['left']
                }
              }
            }
          })

          this.graph.fromJSON({nodes,edges})
          this.graph.centerContent();
        }

        if(this.mindType == 'spaceMap'){
          let nodes = this.dagreLayout.layout(this.objData).nodes
          let edges = (this.objData.edges || []).map(item => {
            return{
              source: item.source, // String，必须，起始节点 id
              target: item.target, // String，必须，目标节点 id
              attrs
            }
          })
          let fristId = nodes[0].id
          nodes = this.setNodeLevel(nodes,fristId)
          let level1 = nodes.filter(item => item.level == 1)
          console.log(level1)
          if(level1 && level1.length > 0){
            level1.forEach((node,index) => {
              if(index % 2 == 0){
                node.x -= (((120 * node.level)) * 2) + 120
                let childNodes = this.getNodeChild(node)
                console.log(childNodes)
                childNodes.forEach(childNode => {
                  let obj = nodes.find(child => child.id == childNode.id)
                  obj.x -= (((120 * obj.level) + (60 * (obj.level - 1))) * 2) + 120
                })
              }
            })
          }
          this.graph.fromJSON({nodes,edges})
          this.graph.centerContent();
        }

      },200)
    },
    //给节点复制level
    setNodeLevel(nodes,fjdId,level = 1){
      // 找出当前层级的节点
      const currentLevelNodes = nodes.filter(item => item.fjdId === fjdId);

      // 为这些节点设置level
      currentLevelNodes.forEach(node => {
        node.level = level;
        // 递归处理子节点
        this.setNodeLevel(nodes, node.id, level + 1);
      });

      return nodes; // 返回原始数组（已修改）
    },
    //获取节点深度(鱼骨图)
    getNodeDepth(item,data, currentDepth = 0){
      const childNode = data.nodes.filter(node => node.fjdId == item.id)
      if(childNode.length == 0) return currentDepth
      let maxDepth = currentDepth
      childNode.forEach(e => {
        const depth = this.getNodeDepth(e, data, currentDepth + 1)
        if(depth > maxDepth) maxDepth = depth
      })
      return maxDepth
    },
    //获取节点下的节点个数(鱼骨图)
    getNodeNum(item,data){
      const childNode = data.nodes.filter(node => node.fjdId == item.id)
      if(childNode.length == 0) return 0
      let maxNum = childNode.length
      childNode.forEach(e => {
        maxNum += this.getNodeNum(e,data)
      })
      return maxNum
    },
    //计算二级后所有节点的位置(鱼骨图)
    buildFish(data,item,isTop){
      let nodes = data.nodes
      let edges = data.edges
      let secendX = item.x
      let secendY = item.y
      nodes.filter(_item => _item.fjdId == item.id).forEach(e => {
        // let childNum = nodes.filter(child => child.fjdId == e.id).length + 1
        //使用递归计算当前节点下的节点个数，算出当前节点的高度，当前节点的平级节点的高度便在此基础上偏移
        let childNum = this.getNodeNum(e, data) + 1
        let args = {}
        if(isTop){
          e.x = secendX + 130
          e.y = secendY - 40
          args = {
            startDirections: ['top'],
            endDirections: ['left']
          }
          secendY -= (50 * childNum)
        }else{
          e.x = secendX + 130
          e.y = secendY + 40
          args = {
            startDirections: ['bottom'],
            endDirections: ['left']
          }
          secendY += (50 * childNum)
        }
        edges.push({
          source: item.id,
          target: e.id,
          router: {
            name: 'manhattan',
            args,
          },
          attrs: {
            line: {
              stroke: '#1d6ee4'
            }
          }
        })
        this.buildFish(data,e,isTop)
      })
    },
    //计算二级后所有节点的位置(树图)
    buildTree(data,item){
      let nodes = data.nodes
      let edges = data.edges
      let secendX = item.x
      let secendY = item.y
      nodes.filter(_item => _item.fjdId == item.id).forEach(e => {
        let childNum = this.getNodeNum(e, data)
        e.x = secendX + 80
        e.y = secendY + 40
        secendY += (childNum + 1) * 40

        edges.push({
          source: item.id,
          target: e.id,
          router: {
            name: 'manhattan',
            args: {
              startDirections: ['bottom'],
              endDirections: ['left']
            }
          },
          attrs: {
            line: {
              stroke: '#1d6ee4'
            }
          }
        })
        this.buildTree(data,e)
      })
    },


    //获取节点数据
    getNodeData(bool,selectNodeId){
      let list = (this.mindData.nodeList || [])
      this.objData.nodes = list.map(item => {
          return {
            id: item.id, // String，可选，节点的唯一标识
            width: 120,   // Number，可选，节点大小的 width 值
            height: 30,  // Number，可选，节点大小的 height 值
            label: item.label, // String，节点标签
            fjdId: item.fjdId,
            data: {
              portalId: item.portalId || '',
              fjdId: item.fjdId || '',
              sort: item.sort,
            },
            attrs: {
              body: {
                rx: 5,
                ry: 5,
              },
              label: {
                fontSize: 12,
                textWrap: {
                  ellipsis: true,
                  width: 105
                }
              }
            }
          }
        })
        this.objData.edges = (this.mindData.edgeList || [])
        //初始化加载mind，更新数据时不初始化mind
        if(bool){
          this.initGraph()
        }else {
          this.graph.cleanSelection()
          this.nowData = {}
          //数据上图
          this.handleCommand(this.mindType)

          //
          if (selectNodeId) {
            const node = this.graph.getCellById(selectNodeId)
            if (node) {
              this.graph.resetSelection(node)
              this.nowData = {
                id: node.id,
                label: node.label,
                portalId: node.data.portalId || ''
              }
              this.$emit('getData', this.nowData)
            }
          }
        }
    },

    //cancelDialog
    cancelDialog(){
      this.visible = false
    },
    //删除节点
    removeNode(){
      if(!this.nowData.id){
        this.$message.error('请选择需要删除的节点')
      }else{
        this.mindData.nodeList = this.mindData.nodeList.filter(item => item.id != this.nowData.id)
        this.mindData.edgeList = this.mindData.edgeList.filter(item => item.target != this.nowData.id)
        this.getNodeData(false)
      }
    },
    //新增节点
    addNode(){
      this.formData = {}
      this.pageType = 'add'
      if (this.objData.nodes.length == 0){
        this.visible = true
      } else{
        if(!this.nowData.id){
          this.$message.error('请选择父节点')
        }else{
          this.visible = true
        }
      }
    },
    //编辑节点
    updateNode(){
      this.formData = {}
      this.pageType = 'edit'
      if(!this.nowData.id){
        this.$message.error('请选择编辑的节点')
      }else{
        this.formData = this.nowData
        this.visible = true
      }
    },
    submitData(){
      // 新增的时候，formData就是新增本身，nowData就是父节点
      // 编辑的时候，获取到nowData，赋值给formData
      this.$refs.formData.validate(valid => {
        if(valid){
          if (this.pageType == 'edit'){
            let obj = this.mindData.nodeList.find(item => item.id == this.formData.id)
            obj.label = this.formData.label
            this.visible = false
            this.getNodeData(false)
          }else{
            let id = Math.random().toString(36).substring(2, 4)
            this.mindData.nodeList.push({
              id, label: this.formData.label,fjdId: this.nowData.id
            })
            this.mindData.edgeList.push({
              target: id, source: this.nowData.id,
            })
            this.visible = false
            this.getNodeData(false)
          }
        }
      })
    },
  }
}
</script>

<style scoped>
.MindMapContent{
  padding: 10px 25px;
  height: 700px;
  background-color: #ffffff;
}
#mindContent{
  position: relative;
}
</style>

